Source

Bernstein, D and Lagakos, SW. Sample size and power determination for stratified clinical trials. Journal of Statistical Computation Simulation, 8:65-73, 1978.

Description

The Two Arm Survival calculator computes an estimate of total sample size and either accrual rate or power for a comparison of survival between two arms.

The calculations are based on the assumptions of uniform accrual over time, no loss to follow-up, exponentially distributed event times, large sample approximation, and use of the exponential MLE test.

Running the Program

The user is prompted for the type of calculation to be performed, either a calculation of sample size required or a calculation of power. The user can also perform the calculation over a specified number of strata (maximum 6). The default setting is to estimate the power for a single stratum. The user is then prompted for the inputs listed below.

All inputs must be in terms of the same time units (e.g. years, months, etc).

For survival calculations involving more than one stratum, all calculations will be performed such that the hazard ratio will remain equal between all strata. For input type of hazard ratio, the hazard ratio input will be used for all strata; for input types of survival proportion and median survival, only the first strata will have prompts for estimates on the experimental arm, and the hazard ratio calculated from these estimates will be automatically applied to the additional strata.

Some input items have initial default values, as indicated in parentheses below.

Input Items

The user is prompted for values to the following items. For items that have initial default values set, the values are given in parentheses.

If input type is hazard ratio:

If input type is survival proportions:

If input type is median survival:

Output Items

Within each stratum, the following will also be output:

If input type is hazard ratio:

If input type is survival proportions:

If input type is median survival:

Interpreting Outputs

Statistical Code

The program is written in R.

View Code


function(calc_type = "Sample Size", input_type = "Hazard Ratio", p_s, sides, accrual, followup, pstrat = 1, p0, t0, hr, p1, t1, accrualRate, alpha, power) {
  
  if (input_type == "Survival Proportions") {
    hr = (-log(p0) / t0) / (-log(p1) / t1)
  }
  if (input_type == "Median Survival") {
    hr = (-log(p0) / t0) / (-log(p1) / t1)
  }
  
  alpha = alpha / sides
  zalpha = qnorm(alpha)
  nstrata = length(p0)
  
  hre_s = array(nstrata)
  hre_e = array(nstrata)
  ps_e = array(nstrata)
  ms_s = array(nstrata)
  ms_e = array(nstrata)
  st_e = array(nstrata)
  
  cpie = array(nstrata)
  epie = array(nstrata)
  
  stratvalues = matrix(, nrow = nstrata, ncol = 6)
  for (i in 1:nstrata) {
    hre_s[i] = -log(p0[i]) / t0[i]
    hre_e[i] = (-log(p0[i]) / t0[i]) / hr
    ps_e[i] = exp(-hre_e[i] * t0[i])
    ms_s[i] = -log(0.5) / hre_s[i]
    ms_e[i] = -log(0.5) / hre_e[i]
    if(input_type == "Hazard Ratio") {st_e[i] = t0[i]}
    else if(input_type == "Survival Proportions" | input_type == "Median Survival") {st_e[i] = t1[i]}
    
    stratvalues[i, 1] = round(hre_s[i], 4)
    stratvalues[i, 2] = round(hre_e[i], 4)
    stratvalues[i, 3] = round(ps_e[i], 3)
    stratvalues[i, 4] = round(ms_s[i], 3)
    stratvalues[i, 5] = round(ms_e[i], 3)
    stratvalues[i, 6] = round(st_e[i], 3)

    
    cpie[i] = 1 - (exp(-hre_s[i] * followup) - exp(-hre_s[i] * accrual - hre_s[i] * followup)) / (accrual * hre_s[i])
    epie[i] = 1 - (exp(-hre_e[i] * followup) - exp(-hre_e[i] * accrual - hre_e[i] * followup)) / (accrual * hre_e[i])
  }
  
  wn = 0
  wa = 0
  
  if (calc_type == "Sample Size") {
    #sample size calculation given input of power
    
    for (i in 1:nstrata) {
      f = pstrat[i] * accrual
      fc = f * p_s
      fe = f - fc

      #set varN, varA to same calculation (alt-alt variance term)
      #this matches the formula in the Bernstein, Lagakos manuscript
      varN = 1 / (fc * cpie[i]) + 1 / (fe * epie[i])
      varA = 1 / (fc * cpie[i]) + 1 / (fe * epie[i])
      wn = wn + 1 / varN
      wa = wa + 1 / varA
    }
    vn = 1 / wn
    va = 1 / wa
    zalpha = -zalpha

    crit = zalpha * sqrt(vn)
    zbeta = qnorm(1 - power)

    accrualRate = (crit - zbeta * sqrt(va)) / log(hr)
    accrualRate = round(accrualRate * accrualRate, 2)
    totalAccrual = floor(accrualRate * accrual)
  } else if (calc_type == "Power") {
    #power calculation given inputs of sample size, accrual rate
   
    for (i in 1:nstrata) {
      f = accrualRate * pstrat[i] * accrual
      fc = f * p_s
      fe = f - fc
      
      #set varN, varA to same calculation (alt-alt variance term)
      #this matches the formula in the Bernstein, Lagakos manuscript
      varN = 1 / (fc * cpie[i]) + 1 / (fe * epie[i])
      varA = 1 / (fc * cpie[i]) + 1. / (fe * epie[i])
      wn = wn + 1 / varN
      wa = wa + 1 / varA
    }
    vn = 1 / wn
    va = 1 / wa
    zalpha = -zalpha
    
    crit = zalpha * sqrt(vn)
    
    power = round(1 - pnorm((crit - log(hr)) / sqrt(va)), 4)
    totalAccrual = floor(accrualRate * accrual)
  
  }
  
  calcvalues = data.frame(Power = power,
                          Sample_Size = totalAccrual,
                          Accrual_Rate = accrualRate,
                          Hazard_Ratio = hr)
  
  stratvalues = as.data.frame(stratvalues)
  names(stratvalues) = c("Hazard_Rate_Std", "Hazard_Rate_Exp", "Prop_Surviving_Exp",
                         "Median_Survival_Std", "Median_Survival_Exp", "Survival_Time_Exp")
  
  output = list(calcvalues = calcvalues,
                stratvalues = stratvalues)

  return(jsonlite::toJSON(output, pretty = TRUE))
}